package com.guli.mall.service.impl;

import com.guli.common.constant.SearchConstant;
import com.guli.common.to.SpuEsTo;
import com.guli.mall.config.EsConfig;
import com.guli.mall.feign.ProductServiceFeignClient;
import com.guli.mall.service.ProductSearchService;
import com.guli.mall.util.EsResponseUtil;
import com.guli.mall.vo.SearchParamsVo;
import com.guli.mall.vo.SearchResultVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.BooleanUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.query.QuerySearchRequest;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/*
 * @Author 罗
 * @date 2020/10/13 - 7:26 下午
 */
@Service
@Slf4j
public class ProductSearchServiceImpl implements ProductSearchService {

    @Resource
    RestHighLevelClient restHighLevelClient;

    @Resource
    ProductServiceFeignClient productServiceFeignClient;

    // 属性聚合
    private static final AggregationBuilder ATTR_AGGREGATION_BUILDER;

    // 品牌聚合
    private static final AggregationBuilder BRAND_ID_AGGREGATION_BUILDER;

    // 分类聚合
    private static final AggregationBuilder CATELOG_ID_AGGREGATION_BUILDER;

    // 一级聚合的名字
    public static final String ATTR_AGG_NAME = "attrAgg";

    public static final String BRAND_ID_AGG_NAME = "brandIdAgg";

    public static final String CATELOG_ID_AGG_NAME = "catelogIdAgg";

    // 二级聚合的名字
    public static final String ATTR_ID_AGG_NAME = "attrIdAgg";

    public static final String BRAND_NAME_AGG_NAME = "brandNameAgg";

    public static final String BRAND_IMG_AGG_NAME = "brandImgAgg";

    public static final String CATELOG_NAME_AGG_NAME = "catelogNameAgg";

    // 三级聚合的名字
    public static final String ATTR_NAME_AGG_NAME = "attrNameAgg";

    // 四级聚合的名字
    public static final String ATTR_NAME_VALUE_AGG_NAME = "attrNameValueAgg";

    public static final Integer SEARCH_DEFAULT_PAGE_SIZE = 30;

    static {
        // 属性聚合 nested 嵌入式
        ATTR_AGGREGATION_BUILDER = AggregationBuilders.nested(ATTR_AGG_NAME, "attrs");

        // 属性聚合的子聚合，聚合属性id
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms(ATTR_ID_AGG_NAME).field("attrs.attrId");
        ATTR_AGGREGATION_BUILDER.subAggregation(attrIdAgg);

        /** 不可以这样设计，属性id的同级聚合，属性名(attrNameAgg)聚合。
         * 这样 attrId 和 attrName 同级聚合之后，attrId 和 attrName 之间的对应关系就被切断了
         * 最终还是要：attrName 是 attrId 的 子聚合才可以
         */
        TermsAggregationBuilder attrNameAgg = AggregationBuilders.terms(ATTR_NAME_AGG_NAME).field("attrs.attrName");
        attrIdAgg.subAggregation(attrNameAgg);

        // 属性名(attrNameAgg)的子聚合
        TermsAggregationBuilder attrNameValueAgg = AggregationBuilders.terms(ATTR_NAME_VALUE_AGG_NAME).field("attrs.attrValue");
        attrNameAgg.subAggregation(attrNameValueAgg);

        // 品牌聚合
        // 品牌id聚合
        BRAND_ID_AGGREGATION_BUILDER = AggregationBuilders.terms(BRAND_ID_AGG_NAME).field("brandId");

        // 品牌名聚合（品牌id的子聚合）
        TermsAggregationBuilder brandNameAgg = AggregationBuilders.terms(BRAND_NAME_AGG_NAME).field("brandName");
        BRAND_ID_AGGREGATION_BUILDER.subAggregation(brandNameAgg);

        //品牌logo聚合（品牌图片，品牌id的子聚合）
        TermsAggregationBuilder brandImgAgg = AggregationBuilders.terms(BRAND_IMG_AGG_NAME).field("brandImg");
        BRAND_ID_AGGREGATION_BUILDER.subAggregation(brandImgAgg);

        // 分类聚合
        // catelogId 聚合
        CATELOG_ID_AGGREGATION_BUILDER = AggregationBuilders.terms(CATELOG_ID_AGG_NAME).field("catelogId");

        // 分类的名称聚合（分类id的子聚合）
        TermsAggregationBuilder catelogNameAgg = AggregationBuilders.terms(CATELOG_NAME_AGG_NAME).field("catelogName");
        CATELOG_ID_AGGREGATION_BUILDER.subAggregation(catelogNameAgg);

        System.out.println("es的查询参数是");
    }

    /**
     * 1、将请求参数封装为 SearchSourceBuilder
     *
     * @param searchParams
     * @return
     */
    private SearchSourceBuilder encapsulateSearParamsToSearchSourceBuilder(SearchParamsVo searchParams) {

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        // 检索 skuTitle
        if (!StringUtils.isEmpty(searchParams.getKeyword())) {

            MatchQueryBuilder skuTitle = QueryBuilders.matchQuery("skuTitle", searchParams.getKeyword());

            boolQueryBuilder.must(skuTitle);
        }

        // 检索属性和属性值
        if (!ObjectUtils.isEmpty(searchParams.getAttrMap()) && searchParams.getAttrMap().size() > 0) {

            /**
             * 为接收到的每一个 attr 都封装成一个 nested query 然后再放入 filter 才能获取到正确的数据
             * */
            searchParams.getAttrMap().forEach((attr) -> {

                BoolQueryBuilder nestedQuery = QueryBuilders.boolQuery();

                nestedQuery.must(QueryBuilders.termQuery("attrs.attrId", attr.getAttrId()));

                nestedQuery.must(QueryBuilders.termsQuery("attrs.attrValue", attr.getAttrValues()));

                NestedQueryBuilder attrs = QueryBuilders.nestedQuery("attrs", nestedQuery, ScoreMode.None);

                boolQueryBuilder.filter(attrs);
            });


        }

        // 检索品牌id
        if (!CollectionUtils.isEmpty(searchParams.getBrandIds())) {
            TermsQueryBuilder brandIdTerms = QueryBuilders.termsQuery("brandId", searchParams.getBrandIds());
            boolQueryBuilder.filter(brandIdTerms);
        }

        // 检索三级分类id
        if (!ObjectUtils.isEmpty(searchParams.getCatalog3Id())) {
            TermQueryBuilder catelogIdTerm = QueryBuilders.termQuery("catelogId", searchParams.getCatalog3Id());
            boolQueryBuilder.filter(catelogIdTerm);
        }

        //  检索是否有库存
        if (!ObjectUtils.isEmpty(searchParams.getHasStock())) {
            TermQueryBuilder hasStockTerm = QueryBuilders.termQuery("hasStock", searchParams.getHasStock());
            boolQueryBuilder.filter(hasStockTerm);
        }

        // 检查价格区间
        if (!ObjectUtils.isEmpty(searchParams.getMinPrice()) || !ObjectUtils.isEmpty(searchParams.getMaxPrice())) {

            RangeQueryBuilder skuPriceRange = QueryBuilders.rangeQuery("skuPrice");

            if (!ObjectUtils.isEmpty(searchParams.getMaxPrice())) {

                skuPriceRange.lte(searchParams.getMaxPrice());
            }

            if (!ObjectUtils.isEmpty(searchParams.getMinPrice())) {
                skuPriceRange.gte(searchParams.getMinPrice());
            }
            boolQueryBuilder.filter(skuPriceRange);
        }




        /*if(!CollectionUtils.isEmpty(searchParams.getBrandIds())) {

            searchParams.getBrandIds().forEach(brandId -> {
                TermQueryBuilder builder = QueryBuilders.termQuery("brandId", brandId);
                boolQueryBuilder.should(builder);
            });
        }*/


        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        searchSourceBuilder.query(boolQueryBuilder);

        // 设置排序，对 单个 sku 的销量进行排序没有意义；而对于价格，我在之后的计算出平均价格，再排序
        /*if (!ObjectUtils.isEmpty(searchParams.getSortCondition())) {

            String[] s = searchParams.getSortCondition().split("_");

            if (s.length == 2) {

                SortOrder sortOrder = "asc".equals(s[1]) ? SortOrder.ASC : SortOrder.DESC;

                searchSourceBuilder.sort(s[0], sortOrder);
            }

        }*/


        // 设置高亮（高亮要有搜索的keyword，才会有意义）
        if (!StringUtils.isEmpty(searchParams.getKeyword())) {
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            HighlightBuilder.Field highlightTitle = new HighlightBuilder.Field("skuTitle");

            highlightTitle.highlighterType("plain");

            highlightBuilder.field(highlightTitle);

            highlightBuilder.preTags("<span style='color:red'>");
            highlightBuilder.postTags("</span>");
            searchSourceBuilder.highlighter(highlightBuilder);
        }

        // 设置聚合
        searchSourceBuilder.aggregation(BRAND_ID_AGGREGATION_BUILDER);
        searchSourceBuilder.aggregation(CATELOG_ID_AGGREGATION_BUILDER);
        searchSourceBuilder.aggregation(ATTR_AGGREGATION_BUILDER);

        return searchSourceBuilder;
    }

    @Override
    public SearchResultVo search(SearchParamsVo searchParamsVo) throws IOException {
        //1、将查询条件解析为 SearchSourceBuilder （不包括分页数据）
        SearchSourceBuilder searchSourceBuilder = this.encapsulateSearParamsToSearchSourceBuilder(searchParamsVo);

        //2、根据封装好的 SearchSourceBuilder 作为查询条件，查出符合条件的 spuIdList，以及 spuId 对应的 skuIdList
        SearchResponse spuIdSearchResponse = aggregateSpuIdAndSkuIdAndReturnSkuIdListByPagination(searchSourceBuilder, searchParamsVo);

        Terms spuMemberAgg = spuIdSearchResponse.getAggregations().get(SPU_NUMBER_AGG);

        List<Integer> skuIdList = new ArrayList<>();

        // 获取到spu的聚合之后，再以spu为单位来设置分页
        int size = ObjectUtils.isEmpty(searchParamsVo.getSize()) ? SEARCH_DEFAULT_PAGE_SIZE : searchParamsVo.getSize();

        int currentPage = ObjectUtils.isEmpty(searchParamsVo.getCurrentPage()) ? 1 : searchParamsVo.getCurrentPage();

        // 开始索引 = (当前页 -1)*每页记录数
        int fromIndex = (currentPage - 1) * size;

        List<? extends Terms.Bucket> spuBuckets = spuMemberAgg.getBuckets();

        // 总的 spu 数量
        int totalSpuCount = spuBuckets.size();

        // 加判断，避免越界异常
        int endIndex = fromIndex + size > totalSpuCount ? totalSpuCount : fromIndex + size;
        /**
         * 3、根据用户发送来的分页数据，以 spuIdList 为单位进行分页
         * */
        IntStream.range(fromIndex, endIndex).mapToObj(i -> {
            return spuBuckets.get(i);

        }).forEach(spuBucket -> {

            /**
             * spuIdList 分页之后获取到页面要显示的 spuId 对应的 skuIdList
             * */
            Terms skuIdAgg = spuBucket.getAggregations().get(SKU_ID_AGG);

            List<Integer> skuIdListOfSpu = skuIdAgg.getBuckets().stream().map(skuBucket -> {
                return skuBucket.getKeyAsNumber().intValue();
            }).collect(Collectors.toList());

            skuIdList.addAll(skuIdListOfSpu);
        });

        // 最终要查询的数据的总量就是剩余的 skuIdList的总量
        searchSourceBuilder.size(skuIdList.size());

        //4、之前 searchSourceBuilder 封装的时候并没有封装 要查询的 skuId（因为不知道），现在将其封装上，并作为过滤条件
        BoolQueryBuilder query = (BoolQueryBuilder) searchSourceBuilder.query();

        query.filter(QueryBuilders.termsQuery("skuId", skuIdList));

        SearchRequest searchRequest = new SearchRequest(SearchConstant.PRODUCT_INDEX);

        log.info("查询条件是：{}", searchSourceBuilder);

        searchRequest.source(searchSourceBuilder);

        SearchResponse search = restHighLevelClient.search(searchRequest, EsConfig.COMMON_OPTIONS);

        log.info("查询到的结果是：{}", search);

        return EsResponseUtil.parseEsResponseToSpuEsTo(search, searchParamsVo.getSize(), totalSpuCount,searchParamsVo,productServiceFeignClient);
    }

    @Override
    public Map<Integer, Integer> searchSpuCountAndItsSkuCount(SearchParamsVo searchParams) throws IOException {

        SearchSourceBuilder searchSourceBuilder = this.encapsulateSearParamsToSearchSourceBuilder(searchParams);

        return null;
    }

    public static final String SPU_NUMBER_AGG = "spuMemberAgg";

    private static final String SKU_ID_AGG = "skuIdAgg";

    private static final AggregationBuilder SEARCH_SPU_ID_AGG;

    private static final AggregationBuilder SEARCH_SKU_ID_AGG;

    static {
        SEARCH_SPU_ID_AGG = AggregationBuilders.terms(SPU_NUMBER_AGG)
                .field("spuId")
                .size(Integer.MAX_VALUE);
        SEARCH_SKU_ID_AGG = AggregationBuilders.terms(SKU_ID_AGG)
                .field("skuId")
                .size(Integer.MAX_VALUE);
        SEARCH_SPU_ID_AGG.subAggregation(SEARCH_SKU_ID_AGG);
    }

    private SearchResponse aggregateSpuIdAndSkuIdAndReturnSkuIdListByPagination(SearchSourceBuilder searchSourceBuilder, SearchParamsVo searchParamsVo) throws IOException {

        searchSourceBuilder.aggregation(SEARCH_SPU_ID_AGG);

        SearchRequest searchRequest = new SearchRequest(SearchConstant.PRODUCT_INDEX);

        searchRequest.source(searchSourceBuilder);

        return restHighLevelClient.search(searchRequest, EsConfig.COMMON_OPTIONS);

    }
}
